<?php

namespace org;

class Alipay {

        protected $return_url;
        protected $notify_url;

        /**
         * 支付宝网关地址（新）
         */
        protected $alipay_gateway_new = 'https://mapi.alipay.com/gateway.do?';
        protected $alipay_config;
        protected $anti_phishing_key;

        /**
         * HTTPS形式消息验证地址
         */
        var $https_verify_url = 'https://mapi.alipay.com/gateway.do?service=notify_verify&';

        /**
         * HTTP形式消息验证地址
         */
        var $http_verify_url = 'http://notify.alipay.com/trade/notify_query.do?';

        public function __construct() {

                $this->return_url = U("/alipay_return@" . 'www', '', '', false, true);
                $this->notify_url = U("/alipay_notify@" . 'www', '', '', false, true);
                //合作身份者id，以2088开头的16位纯数字
                $alipay_config['partner'] = C('payment.partner');

                //安全检验码，以数字和字母组成的32位字符
                $alipay_config['key'] = C('payment.key');


//↑↑↑↑↑↑↑↑↑↑请在这里配置您的基本信息↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
//签名方式 不需修改
                $alipay_config['sign_type'] = strtoupper('MD5');
//字符编码格式 目前支持 gbk 或 utf-8
                $alipay_config['input_charset'] = strtolower('utf-8');
                //ca证书路径地址，用于curl中ssl校验
//请保证cacert.pem文件在当前文件夹目录中
                //     $alipay_config['cacert'] = getcwd() . '\\cacert.pem';
//访问模式,根据自己的服务器是否支持ssl访问，若支持请选择https；若不支持请选择http
                $alipay_config['transport'] = 'http';
                $this->alipay_config = $alipay_config;
                $this->anti_phishing_key = $this->query_timestamp();
        }

        public function Alipay() {
                $this->__construct();
        }

        /**
         * 生成签名结果
         * @param $para_sort 已排序要签名的数组
         * return 签名结果字符串
         */
        protected function buildRequestMysign($para_sort) {
                //把数组所有元素，按照“参数=参数值”的模式用“&”字符拼接成字符串
                $prestr = $this->createLinkstring($para_sort);

                $mysign = "";
                switch (strtoupper(trim($this->alipay_config['sign_type']))) {
                        case "MD5" :
                                $mysign = $this->md5Sign($prestr, $this->alipay_config['key']);
                                break;
                        default :
                                $mysign = "";
                }

                return $mysign;
        }

        /**
         * 生成要请求给支付宝的参数数组
         * @param $para_temp 请求前的参数数组
         * @return 要请求的参数数组
         */
        protected function buildRequestPara($para_temp) {
                //除去待签名参数数组中的空值和签名参数
                $para_filter = $this->paraFilter($para_temp);

                //对待签名参数数组排序
                $para_sort = $this->argSort($para_filter);

                //生成签名结果
                $mysign = $this->buildRequestMysign($para_sort);

                //签名结果与签名方式加入请求提交参数组中
                $para_sort['sign'] = $mysign;
                $para_sort['sign_type'] = strtoupper(trim($this->alipay_config['sign_type']));

                return $para_sort;
        }

        /**
         * 生成要请求给支付宝的参数数组
         * @param $para_temp 请求前的参数数组
         * @return 要请求的参数数组字符串
         */
        protected function buildRequestParaToString($para_temp) {
                //待请求参数数组
                $para = $this->buildRequestPara($para_temp);

                //把参数组中所有元素，按照“参数=参数值”的模式用“&”字符拼接成字符串，并对字符串做urlencode编码
                $request_data = $this->createLinkstringUrlencode($para);

                return $request_data;
        }

        /**
         * 建立请求，以表单HTML形式构造（默认）
         * @param $para_temp 请求参数数组
         * @param $method 提交方式。两个值可选：post、get
         * @param $button_name 确认按钮显示文字
         * @return 提交表单HTML文本
         */
        public function buildRequestForm($para_temp, $method) {

                //构造要请求的参数数组，无需改动
                $parameter = array(
                    "service" => "create_direct_pay_by_user",
                    "partner" => trim($this->alipay_config['partner']),
                    "payment_type" => "1",
                    "notify_url" => $this->notify_url,
                    "return_url" => $this->return_url,
                    "seller_email" => C('payment.seller_email'),
                    "out_trade_no" => $para_temp['order_id'],
                    "subject" => $para_temp['subject'],
                    "total_fee" => $para_temp['prepay_ment'],
                    "body" => $para_temp['body'],
                    "show_url" => $para_temp['show_url'],
                    "anti_phishing_key" => $this->anti_phishing_key,
                    "exter_invoke_ip" => get_client_ip(),
                    "_input_charset" => trim(strtolower($this->alipay_config['input_charset']))
                );
                $para_temp['paymethod'] = '';
                if ($para_temp['paymethod'] != '') {
                        if ($para_temp['paymethod'] == 'bankPay') {
                                $parameter["paymethod"] = "bankPay";
                                $parameter["defaultbank"] = $para_temp['defaultbank'];
                        }
                }
                //待请求参数数组
                $para = $this->buildRequestPara($parameter);

                $sHtml = "<form id='alipaysubmit' name='alipaysubmit' action='" . $this->alipay_gateway_new . "_input_charset=" . trim(strtolower($this->alipay_config['input_charset'])) . "' method='" . $method . "'>";
                while (list ($key, $val) = each($para)) {
                        $sHtml.= "<input type='hidden' name='" . $key . "' value='" . $val . "'/>";
                }

                $sHtml .= "跳转中……";
                $sHtml = $sHtml . "<script>document.forms['alipaysubmit'].submit();</script>";

                return $sHtml;
        }

        /**
         * 建立请求，以模拟远程HTTP的POST请求方式构造并获取支付宝的处理结果
         * @param $para_temp 请求参数数组
         * @return 支付宝处理结果
         */
        protected function buildRequestHttp($para_temp) {

                //构造要请求的参数数组，无需改动
                $parameter = array(
                    "service" => "create_direct_pay_by_user",
                    "partner" => trim($this->alipay_config['partner']),
                    "payment_type" => "1",
                    "notify_url" => $this->notify_url,
                    "return_url" => $this->return_url,
                    "seller_email" => C("payment.seller_email"),
                    "out_trade_no" => $para_temp['order_id'],
                    "subject" => $para_temp['subject'],
                    "total_fee" => $para_temp['prepay_ment'],
                    "body" => $para_temp['body'],
                    "show_url" => $para_temp['show_url'],
                    "anti_phishing_key" => $this->anti_phishing_key,
                    "exter_invoke_ip" => get_client_ip(),
                    "_input_charset" => trim(strtolower($this->alipay_config['input_charset']))
                );

                $para_temp['paymethod'] = '';
                if ($para_temp['paymethod'] != '') {
                        if ($para_temp['paymethod'] == 'bankPay') {
                                $parameter["paymethod"] = "bankPay";
                                $parameter["defaultbank"] = $para_temp['pay_bank'];
                        }
                }
                $sResult = '';

                //待请求参数数组字符串
                $request_data = $this->buildRequestPara($parameter);
                //远程获取数据
                $sResult = $this->getHttpResponsePOST($this->alipay_gateway_new, $this->alipay_config['cacert'], $request_data, trim(strtolower($this->alipay_config['input_charset'])));

                return $sResult;
        }

        /**
         * 用于防钓鱼，调用接口query_timestamp来获取时间戳的处理函数
         * 注意：该功能PHP5环境及以上支持，因此必须服务器、本地电脑中装有支持DOMDocument、SSL的PHP配置环境。建议本地调试时使用PHP开发软件
         * return 时间戳字符串
         */
        protected function query_timestamp() {
                $url = $this->alipay_gateway_new . "service=query_timestamp&partner=" . trim(strtolower($this->alipay_config['partner']));
                $encrypt_key = "";

                $doc = new \DOMDocument();
                $doc->load($url);
                $itemEncrypt_key = $doc->getElementsByTagName("encrypt_key");
                $encrypt_key = $itemEncrypt_key->item(0)->nodeValue;

                return $encrypt_key;
        }

        /**
         * 签名字符串
         * @param $prestr 需要签名的字符串
         * @param $key 私钥
         * return 签名结果
         */
        protected function md5Sign($prestr, $key) {
                $prestr = $prestr . $key;
                return md5($prestr);
        }

        /**
         * 验证签名
         * @param $prestr 需要签名的字符串
         * @param $sign 签名结果
         * @param $key 私钥
         * return 签名结果
         */
        protected function md5Verify($prestr, $sign, $key) {
                $prestr = $prestr . $key;
                $mysgin = md5($prestr);

                if ($mysgin == $sign) {
                        return true;
                } else {
                        return false;
                }
        }

        /**
         * 把数组所有元素，按照“参数=参数值”的模式用“&”字符拼接成字符串
         * @param $para 需要拼接的数组
         * return 拼接完成以后的字符串
         */
        protected function createLinkstring($para) {
                $arg = "";
                while (list ($key, $val) = each($para)) {
                        $arg.=$key . "=" . $val . "&";
                }
                //去掉最后一个&字符
                $arg = substr($arg, 0, count($arg) - 2);

                //如果存在转义字符，那么去掉转义
                if (get_magic_quotes_gpc()) {
                        $arg = stripslashes($arg);
                }

                return $arg;
        }

        /**
         * 把数组所有元素，按照“参数=参数值”的模式用“&”字符拼接成字符串，并对字符串做urlencode编码
         * @param $para 需要拼接的数组
         * return 拼接完成以后的字符串
         */
        protected function createLinkstringUrlencode($para) {
                $arg = "";
                while (list ($key, $val) = each($para)) {
                        $arg.=$key . "=" . urlencode($val) . "&";
                }
                //去掉最后一个&字符
                $arg = substr($arg, 0, count($arg) - 2);

                //如果存在转义字符，那么去掉转义
                if (get_magic_quotes_gpc()) {
                        $arg = stripslashes($arg);
                }

                return $arg;
        }

        /**
         * 除去数组中的空值和签名参数
         * @param $para 签名参数组
         * return 去掉空值与签名参数后的新签名参数组
         */
        protected function paraFilter($para) {
                $para_filter = array();
                while (list ($key, $val) = each($para)) {
                        if ($key == "sign" || $key == "sign_type" || $val == "")
                                continue;
                        else
                                $para_filter[$key] = $para[$key];
                }
                return $para_filter;
        }

        /**
         * 对数组排序
         * @param $para 排序前的数组
         * return 排序后的数组
         */
        protected function argSort($para) {
                ksort($para);
                reset($para);
                return $para;
        }

        /**
         * 远程获取数据，POST模式
         * 注意：
         * 1.使用Crul需要修改服务器中php.ini文件的设置，找到php_curl.dll去掉前面的";"就行了
         * 2.文件夹中cacert.pem是SSL证书请保证其路径有效，目前默认路径是：getcwd().'\\cacert.pem'
         * @param $url 指定URL完整路径地址
         * @param $cacert_url 指定当前工作目录绝对路径
         * @param $para 请求的数据
         * @param $input_charset 编码格式。默认值：空值
         * return 远程输出的数据
         */
        protected function getHttpResponsePOST($url, $cacert_url, $para, $input_charset = '') {

                if (trim($input_charset) != '') {
                        $url = $url . "_input_charset=" . $input_charset;
                }
                $curl = curl_init($url);
                curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true); //SSL证书认证
                curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2); //严格认证
                curl_setopt($curl, CURLOPT_CAINFO, $cacert_url); //证书地址
                curl_setopt($curl, CURLOPT_HEADER, 0); // 过滤HTTP头
                curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); // 显示输出结果
                curl_setopt($curl, CURLOPT_POST, true); // post传输数据
                curl_setopt($curl, CURLOPT_POSTFIELDS, $para); // post传输数据
                $responseText = curl_exec($curl);
                var_dump(curl_error($curl)); //如果执行curl过程中出现异常，可打开此开关，以便查看异常内容
                curl_close($curl);

                return $responseText;
        }

        /**
         * 远程获取数据，GET模式
         * 注意：
         * 1.使用Crul需要修改服务器中php.ini文件的设置，找到php_curl.dll去掉前面的";"就行了
         * 2.文件夹中cacert.pem是SSL证书请保证其路径有效，目前默认路径是：getcwd().'\\cacert.pem'
         * @param $url 指定URL完整路径地址
         * @param $cacert_url 指定当前工作目录绝对路径
         * return 远程输出的数据
         */
        protected function getHttpResponseGET($url, $cacert_url) {
                $curl = curl_init($url);
                curl_setopt($curl, CURLOPT_HEADER, 0); // 过滤HTTP头
                curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); // 显示输出结果
                curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true); //SSL证书认证
                curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2); //严格认证
                curl_setopt($curl, CURLOPT_CAINFO, $cacert_url); //证书地址
                $responseText = curl_exec($curl);
                //var_dump( curl_error($curl) );//如果执行curl过程中出现异常，可打开此开关，以便查看异常内容
                curl_close($curl);

                return $responseText;
        }

        /**
         * 实现多种字符编码方式
         * @param $input 需要编码的字符串
         * @param $_output_charset 输出的编码格式
         * @param $_input_charset 输入的编码格式
         * return 编码后的字符串
         */
        protected function charsetEncode($input, $_output_charset, $_input_charset) {
                $output = "";
                if (!isset($_output_charset))
                        $_output_charset = $_input_charset;
                if ($_input_charset == $_output_charset || $input == null) {
                        $output = $input;
                } elseif (function_exists("mb_convert_encoding")) {
                        $output = mb_convert_encoding($input, $_output_charset, $_input_charset);
                } elseif (function_exists("iconv")) {
                        $output = iconv($_input_charset, $_output_charset, $input);
                } else
                        die("sorry, you have no libs support for charset change.");
                return $output;
        }

        /**
         * 实现多种字符解码方式
         * @param $input 需要解码的字符串
         * @param $_output_charset 输出的解码格式
         * @param $_input_charset 输入的解码格式
         * return 解码后的字符串
         */
        protected function charsetDecode($input, $_input_charset, $_output_charset) {
                $output = "";
                if (!isset($_input_charset))
                        $_input_charset = $_input_charset;
                if ($_input_charset == $_output_charset || $input == null) {
                        $output = $input;
                } elseif (function_exists("mb_convert_encoding")) {
                        $output = mb_convert_encoding($input, $_output_charset, $_input_charset);
                } elseif (function_exists("iconv")) {
                        $output = iconv($_input_charset, $_output_charset, $input);
                } else
                        die("sorry, you have no libs support for charset changes.");
                return $output;
        }

        /**
         * 针对notify_url验证消息是否是支付宝发出的合法消息
         * @return 验证结果
         */
        public function verifyNotify() {
                if (empty($_POST)) {//判断POST来的数组是否为空
                        return false;
                } else {
                        //生成签名结果
                        $isSign = $this->getSignVeryfy($_POST, $_POST["sign"]);
                        //获取支付宝远程服务器ATN结果（验证是否是支付宝发来的消息）
                        $responseTxt = 'true';
                        if (!empty($_POST["notify_id"])) {
                                $responseTxt = $this->getResponse($_POST["notify_id"]);
                        }

                        //写日志记录
                        //if ($isSign) {
                        //	$isSignStr = 'true';
                        //}
                        //else {
                        //	$isSignStr = 'false';
                        //}
                        //$log_text = "responseTxt=".$responseTxt."\n notify_url_log:isSign=".$isSignStr.",";
                        //$log_text = $log_text.createLinkString($_POST);
                        //logResult($log_text);
                        //验证
                        //$responsetTxt的结果不是true，与服务器设置问题、合作身份者ID、notify_id一分钟失效有关
                        //isSign的结果不是true，与安全校验码、请求时的参数格式（如：带自定义参数等）、编码格式有关
                        if (preg_match("/true$/i", $responseTxt) && $isSign) {
                                return true;
                        } else {
                                return false;
                        }
                }
        }

        /**
         * 针对return_url验证消息是否是支付宝发出的合法消息
         * @return 验证结果
         */
        public function verifyReturn() {
                if (empty($_GET)) {//判断POST来的数组是否为空
                        return false;
                } else {
                        //生成签名结果
                        $isSign = $this->getSignVeryfy($_GET, $_GET["sign"]);
                        //获取支付宝远程服务器ATN结果（验证是否是支付宝发来的消息）
                        $responseTxt = 'true';
                        if (!empty($_GET["notify_id"])) {
                                $responseTxt = $this->getResponse($_GET["notify_id"]);
                        }

                        if (preg_match("/true$/i", $responseTxt) && $isSign) {
                                return true;
                        } else {
                                return false;
                        }
                }
        }

        /**
         * 获取返回时的签名验证结果
         * @param $para_temp 通知返回来的参数数组
         * @param $sign 返回的签名结果
         * @return 签名验证结果
         */
        protected function getSignVeryfy($para_temp, $sign) {
                //除去待签名参数数组中的空值和签名参数
                $para_filter = $this->paraFilter($para_temp);

                //对待签名参数数组排序
                $para_sort = $this->argSort($para_filter);

                //把数组所有元素，按照“参数=参数值”的模式用“&”字符拼接成字符串
                $prestr = $this->createLinkstring($para_sort);

                $isSgin = false;
                switch (strtoupper(trim($this->alipay_config['sign_type']))) {
                        case "MD5" :
                                $isSgin = $this->md5Verify($prestr, $sign, $this->alipay_config['key']);
                                break;
                        default :
                                $isSgin = false;
                }

                return $isSgin;
        }

        /**
         * 获取远程服务器ATN结果,验证返回URL
         * @param $notify_id 通知校验ID
         * @return 服务器ATN结果
         * 验证结果集：
         * invalid命令参数不对 出现这个错误，请检测返回处理中partner和key是否为空 
         * true 返回正确信息
         * false 请检查防火墙或者是服务器阻止端口问题以及验证时间是否超过一分钟
         */
        protected function getResponse($notify_id) {
                $transport = strtolower(trim($this->alipay_config['transport']));
                $partner = trim($this->alipay_config['partner']);
                $veryfy_url = '';
                if ($transport == 'https') {
                        $veryfy_url = $this->https_verify_url;
                } else {
                        $veryfy_url = $this->http_verify_url;
                }
                $veryfy_url = $veryfy_url . "partner=" . $partner . "&notify_id=" . $notify_id;
                $responseTxt = $this->getHttpResponseGET($veryfy_url, $this->alipay_config['cacert']);

                return $responseTxt;
        }

}
